#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#include "config.h"
#include "symbol.h"
#include "list.h"
#include "parser.h"
#include "debug.h"

#define HASH_SIZE (26 + 1)

extern struct cminus_symbol *current_function;
extern int current_level;
extern int current_block;

struct list_head sym_table[HASH_SIZE];

static int calc_offset(char *name);

/*init symbol table*/
void sym_table_init()
{
  int i = 0;

  for (i = 0; i < HASH_SIZE; i++)
  {
    INIT_LIST_HEAD(&sym_table[i]);
  }

  return;
}

/*insert var symbol*/
int insert_global_var_symbol(struct cminus_symbol *sym)
{
  int offset = 0;

  if (lookup_global_var(sym->name))
  {
    semantic_error("symbol already exist\n");
    return -1;
  }

  offset = calc_offset(sym->name);
  if (-1 == offset)
  {
    __ERROR_MSG("wrong id name\n");

    if (sym != NULL)
    {
      free(sym);
    }

    return -1;
  }

  list_add_tail(&sym->sym_list, &sym_table[offset]);

  return 0;
}

/*insert function symbol*/
struct cminus_symbol *insert_function_symbol(char *name)
{
  int offset = 0;
  struct cminus_symbol *sym = NULL;

  if (NULL == name)
  {
    __ERROR_MSG("parameter wong\n");
    return NULL;
  }

  if (lookup_function(name))
  {
    semantic_error("symbol already exist\n");
    return NULL;
  }

  sym = malloc(sizeof(struct cminus_symbol));

  if (NULL == sym)
  {
    __ERROR_MSG("error when malloc\n");
    return NULL;
  }

  strncpy(sym->name, name, MAX_NAME_SZ);
  sym->sym_type = FUNCTION;
  sym->u.f.param_size = 0;
  INIT_LIST_HEAD(&sym->u.f.auto_head);
  INIT_LIST_HEAD(&sym->u.f.param_head);

  offset = calc_offset(name);
  if (-1 == offset)
  {
    __ERROR_MSG("wrong id name\n");
    return NULL;
  }

  list_add_tail(&sym->sym_list, &sym_table[offset]);

  return sym;
}

/*lookup global var symbol*/
struct cminus_symbol *lookup_global_var(char *name)
{
  int offset = 0;
  struct cminus_symbol *sym = NULL;
  struct list_head *temp = NULL;

  offset = calc_offset(name);

  if (-1 == offset)
  {
    __ERROR_MSG("wrong id name\n");
    return NULL;
  }

  list_for_each(temp, &sym_table[offset])
  {
    sym = list_entry(temp,  struct cminus_symbol, sym_list);
    if (0 == strcmp(sym->name, name) && GLOBAL_VAR == sym->sym_type)
    {
      return sym;
    }
  }

  return NULL;
}

struct cminus_symbol *lookup_function(char *name)
{
  int offset = 0;
  struct cminus_symbol *sym = NULL;
  struct list_head *temp = NULL;

  offset = calc_offset(name);

  if (-1 == offset)
  {
    __ERROR_MSG("wrong id name\n");
    return NULL;
  }

  list_for_each(temp, &sym_table[offset])
  {
    sym = list_entry(temp,  struct cminus_symbol, sym_list);
    if (0 == strcmp(sym->name, name) && FUNCTION == sym->sym_type)
    {
      return sym;
    }
  }

  return NULL;
}

struct auto_info *lookup_auto_var(char *name)
{
  struct list_head *temp = NULL;
  struct auto_info *_auto_info = NULL;
  int block_no = -1; 

  list_for_each(temp, &current_function->u.f.auto_head)
  {
    _auto_info = list_entry(temp, struct auto_info, auto_list);
    if (0 == strcmp(_auto_info->name, name))
    {
      if (_auto_info->block_no == 0 
          || (_auto_info->block_no <= current_block
               && _auto_info->block_no > current_block - current_level))
      {
        if (_auto_info->block_no > block_no)
        {
          block_no = _auto_info->block_no;
        }
      }
    }
  }

  list_for_each(temp, &current_function->u.f.auto_head)
  {
    _auto_info = list_entry(temp, struct auto_info, auto_list);
    if (0 == strcmp(_auto_info->name, name)
        && _auto_info->block_no == block_no)
    {
      return _auto_info;
    }
  }

  return NULL;
}

struct param_info *lookup_param(char *name)
{
  struct list_head *temp = NULL;
  struct param_info *_param_info = NULL;

  list_for_each(temp, &current_function->u.f.param_head)
  {
    _param_info = list_entry(temp, struct param_info, param_list);
    if (0 == strcmp(_param_info->name, name))
    {
      return _param_info;
    }
  }

  return NULL;
}

static int calc_offset(char *name)
{
  char c = *(name);

  if (c != '_' && !(c >= 'A' && c <= 'Z') && !(c >= 'a' && c <= 'z'))
  {
    __ERROR_MSG("wrong id name\n");
    return -1;
  }

  if ('_' == c)
  {
    return HASH_SIZE - 1;
  }
  else
  {
    return tolower(c) - 'a';
  }
}
